Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a standalone Modal layout #7083

Draft
wants to merge 35 commits into
base: main
Choose a base branch
from
Draft

Add a standalone Modal layout #7083

wants to merge 35 commits into from

Conversation

hoxbro
Copy link
Member

@hoxbro hoxbro commented Aug 6, 2024

Fixes #4700

Heavily inspired by https://github.com/awesome-panel/panel-modal. Thank you @MarcSkovMadsen ❤️

import panel as pn
import holoviews as hv
from panel.layout.modal import Modal

pn.extension("modal")
hv.extension("bokeh")

reviews = "test"
close_method = pn.widgets.Button(name="Close (method)")
open_method = pn.widgets.Button(name="Open (method)").servable()
close_parameter = pn.widgets.Button(name="Close (parameter)")
open_parameter = pn.widgets.Button(name="Open (parameter)").servable()
modal = Modal(reviews, close_method, close_parameter, hv.Curve([]), width=1000, height=1500).servable()

close_method.on_click(lambda event: modal.hide())
open_method.on_click(lambda event: modal.show())
close_parameter.on_click(lambda event: setattr(modal, "open", False))
open_parameter.on_click(lambda event: setattr(modal, "open", True))

close_method.on_click(lambda event: print(modal.open))
open_method.on_click(lambda event: print(modal.open))
close_parameter.on_click(lambda event: print(modal.open))
open_parameter.on_click(lambda event: print(modal.open))
screen-recording-2024-08-06_13.32.58.mp4

TO DO:

  • Add tests
  • Add documentation
  • Look into objects showing up before the modal
  • Improve height and width
  • Maybe look into window as any.

@hoxbro hoxbro changed the title Modal Add a standalone Modal layout Aug 6, 2024
@hoxbro hoxbro marked this pull request as draft August 6, 2024 11:40

width = param.Integer(default=None, bounds=(0, None))

is_open = param.Boolean(default=False, readonly=True, doc="Whether the modal is open.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this PR.

I've experienced that for some use cases I would like the open and close methods. For some it was better if I could set is_open=True or is_open=False. Please consider the last feature :-)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MarcSkovMadsen I also missed these two methods in panel_modal. I ended up using modal._show().

@@ -0,0 +1,42 @@
from __future__ import annotations
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding documentation please consider if request awesome-panel/panel-modal#3 should be part of the docs. Thx.

Copy link

codecov bot commented Aug 9, 2024

Codecov Report

Attention: Patch coverage is 0% with 59 lines in your changes missing coverage. Please review.

Project coverage is 82.12%. Comparing base (7afcc90) to head (9d531b1).

Files with missing lines Patch % Lines
panel/layout/modal.py 0.00% 32 Missing ⚠️
panel/models/modal.py 0.00% 27 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7083      +/-   ##
==========================================
- Coverage   82.21%   82.12%   -0.09%     
==========================================
  Files         339      341       +2     
  Lines       51319    51378      +59     
==========================================
+ Hits        42193    42196       +3     
- Misses       9126     9182      +56     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@contang0
Copy link

This is an excellent addition. I'm a heavy user of panel_modal, and always wished that it shipped together with panel. Modal windows allow me to save a lot of space in the app.

One thing I couldn't get panel_modal to do is to update graphs once they have been created (Tabulator meanwhile updates fine), I hope this development will address it.

@MarcSkovMadsen MarcSkovMadsen mentioned this pull request Aug 31, 2024
@ahuang11
Copy link
Contributor

Anything I can help with to get this PR merged?

@hoxbro
Copy link
Member Author

hoxbro commented Oct 21, 2024

Anything I can help with to get this PR merged?

No, but thank you for asking. I will soon start working on this again.

@hoxbro hoxbro added this to the v1.6.0 milestone Oct 21, 2024
@@ -0,0 +1,62 @@
from __future__ import annotations

Copy link
Collaborator

@MarcSkovMadsen MarcSkovMadsen Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to consider is if the Modal could be made easier to use? Could the api of the Modal be generalized to a concept of actions/ action buttons.

Could the api be simplified?

The api of panel-modal that I made is:

import panel as pn

from panel_modal import Modal

pn.extension("modal")

modal = Modal(pn.panel("Hi. I am the Panel Modal!", width=200))

pn.Column(modal.param.open, modal).servable()

It requires you define a variable holding the Modal and include both the button (modal.param.open) and the modal modal.

If you want to customize the button it requires something like pn.widgets.Button.from_param(modal.param.open as well.

Could the api be simplified? Could it be enough to just include the modal?

import panel as pn

from panel_modal import Modal

pn.extension("modal")

pn.Column(
    Modal(pn.panel("Hi. I am the Panel Modal!", width=200))
).servable()

That should show the button. And when clicked the modal should open/ close. That would make it much simpler to drop in as you don't need to define a variable holding the Modal.

If you want to customize the button you can just do:

import panel as pn

from panel_modal import Modal

pn.extension("modal")

pn.Column(
    pn.widgets.Button.from_param(
        Modal(
            pn.panel("Hi. I am the Panel Modal!", width=200),
        ),
        button_style="outline",
    )
)
).servable()

If you want to trigger the modal from something else than a Button than you should be able to hide the button:

import panel as pn

from panel_modal import Modal

pn.extension("modal")

modal = Modal(..., visible=False)

...

def trigger_modal():
    modal.open=True

pn.Column(
    modal,
    .....
).servable()

Could the API be generalized?

A Modal is actually a part of a general concept for action buttons. You want to be able to trigger an action via a Button. You want to be able to easily set the source or target (object) and be able to customize the button.

modal_button = ModalButton(object=pn.panel(...))
copy_text_button= CopyToClipboardButton(object="...my code")
copy_dataframe_button= CopyToClipboardButton(object=df) # df is a dataframe
paste_button = PasteFromClipboardButton(object=pn.widgets.Tabulator) # Here the object is the target. Not the source.
link_button = OpenLinkButton(object="https://panel.holoviz.org, target="_blank")
maximize_content = pn.Column(...)
maximize_button = MaximizeButton(object=maximize_content) # Maximizes the object to full window size
...

I've had in the back of my head that I wanted to create a panel-action-buttons package one day. Compiling these as I use those for my work app all the time.

@philippjfr. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a standalone Modal layout
6 participants